/*
 * Copyright (c) Huawei Device Co., Ltd. 2023. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "channel_impl.h"
#include "common_utils.h"
#include "se_common_exception.h"
#include "se_hianalytics.h"
#include "./utils/apdu_validator.h"
#include "secure_element_agent.h"

static constexpr int FIRST_CLA_MAX_CHANNEL = 3;
static constexpr uint32_t FIRST_CLA_MASK_BIT7 = 0xBC; // 1011 1100
static constexpr int FURTHER_CLA_MAX_CHANNEL = 19;
static constexpr uint32_t FURTHER_CLA_LOGICAL_CHANNEL = 4;
static constexpr uint32_t FURTHER_CLA_MASK_BIT7 = 0x40; // 0100 0000
static constexpr uint32_t FURTHER_CLA_CLEAR_MASK = 0xB0; // 1011 0000

namespace OHOS {
namespace SE {
ChannelImpl::ChannelImpl(std::weak_ptr<SecureElementAgent> agent, bool isBasic, uint8_t channelNumber,
    const std::string &selectResponse): boundHdfAgent_(agent), isBasicChannel_(isBasic), channelNumber_(channelNumber),
    isChannelClosed_(false), selectResponse_(selectResponse)
{
    InfoLog("ChannelImpl::ChannelImpl");
}

ChannelImpl::~ChannelImpl()
{
    InfoLog("ChannelImpl::~ChannelImpl channelNumber_ = %{public}d", channelNumber_);
}

ErrorCode ChannelImpl::Close(void)
{
    std::lock_guard<std::mutex> lock(mutex_);
    if (isChannelClosed_) {
        ErrorLog("ChannelImpl::Close isChannelClosed_ already.");
        return ErrorCode::SE_SUCCESS;
    }
    if (boundHdfAgent_.expired()) {
        ErrorLog("ChannelImpl::Close SE_ILLEGAL_STATE_ERROR");
        if (!isBasicChannel_) {
            SeHiAnalytics::GetInstance().SeEventLog(LOG_CLOSE_LOGICAL_CHANNEL_FAIL, std::to_string(channelNumber_));
        }
        return ErrorCode::SE_ILLEGAL_STATE_ERROR;
    }
    boundHdfAgent_.lock()->CloseChannel(channelNumber_, isBasicChannel_, CommonUtils::GetUniqueTag());
    isChannelClosed_ = true;
    return ErrorCode::SE_SUCCESS;
}

ErrorCode ChannelImpl::IsClosed(bool& isClosed)
{
    std::lock_guard<std::mutex> lock(mutex_);
    isClosed = isChannelClosed_;
    return ErrorCode::SE_SUCCESS;
}

ErrorCode ChannelImpl::IsBasicChannel(bool &isBasicChannel)
{
    std::lock_guard<std::mutex> lock(mutex_);
    isBasicChannel = isBasicChannel_;
    return ErrorCode::SE_SUCCESS;
}

ErrorCode ChannelImpl::GetSelectResponse(std::string &selectResponse)
{
    std::lock_guard<std::mutex> lock(mutex_);
    if (boundHdfAgent_.expired() || isChannelClosed_) {
        ErrorLog("ChannelImpl::GetSelectResponse SE_ILLEGAL_STATE_ERROR");
        selectResponse = std::string();
        return ErrorCode::SE_ILLEGAL_STATE_ERROR;
    }
    selectResponse = selectResponse_;
    return ErrorCode::SE_SUCCESS;
}

static void CheckCommandHeadValid(const std::vector<uint8_t> &commandArray)
{
    if ((commandArray[CMD_HEAD_CLA] & 0x80) == 0 && (commandArray[CMD_HEAD_CLA] & 0x60) != 0x20) {
        if (commandArray[CMD_HEAD_INS] == 0x70) {
            throw IllegalArgumentError("MANAGE CHANNEL command not allowed");
        }
    }
}

static uint8_t UpdateApduChannelId(uint8_t cla, uint8_t channelNumber)
{
    // CLA indicates the class of the command. Due to specifications in ISO/IEC 7816-3, the value 'FF' is invalid. Bit
    // 8 of CLA distinguishes between the interindustry class and the proprietary class.
    // specifies 000x xxxx as the first interindustry values. 01xx xxxx as further interindustry values
    if (channelNumber <= FIRST_CLA_MAX_CHANNEL) {
        // b7 = 0 indicates the first interindustry class byte coding
        cla = (cla & FIRST_CLA_MASK_BIT7) | static_cast<uint8_t>(channelNumber);
    } else if (channelNumber <= FURTHER_CLA_MAX_CHANNEL) {
        // Further interindustry values of CLA: Bits 4 to 1 encode a number from zero to fifteen;
        // this number plus four is
        // the logical channel number from four to nineteen
        cla = (cla & FURTHER_CLA_CLEAR_MASK) | FURTHER_CLA_MASK_BIT7 | (static_cast<uint8_t>(channelNumber) -
            FURTHER_CLA_LOGICAL_CHANNEL);
    } else {
        throw IllegalArgumentError("Channel number must be within [0..19]");
    }
    DebugLog("ChannelImpl::UpdateApduChannelId cla : %{public}d", cla);
    return cla;
}

ErrorCode ChannelImpl::Transmit(std::string const& command, std::string& response)
{
    std::lock_guard<std::mutex> lock(mutex_);
    if (boundHdfAgent_.expired() || isChannelClosed_) {
        ErrorLog("ChannelImpl::Transmit SE_ILLEGAL_STATE_ERROR");
        response = std::string();
        return ErrorCode::SE_ILLEGAL_STATE_ERROR;
    }

    // Validate the APDU command format
    ApduValidator::CheckCommand(command);
    uint32_t commandLength = CommonUtils::GetHexStringLength(command);
    std::vector<uint8_t> commandArray;
    commandArray.reserve(commandLength);
    CommonUtils::HexStringToBytesArray(command, commandArray);
    CheckCommandHeadValid(commandArray);

    // set channel number bits
    commandArray[CMD_HEAD_CLA] = UpdateApduChannelId(commandArray[CMD_HEAD_CLA], channelNumber_);
    std::string newCommand = CommonUtils::BytesArrayToHexString(&commandArray[0], commandLength);
    response = boundHdfAgent_.lock()->Transmit(newCommand);
    return ErrorCode::SE_SUCCESS;
}

ErrorCode ChannelImpl::SelectNext(bool& selectStatus)
{
    WarnLog("ChannelImpl::SelectNext unimpl.");
    std::lock_guard<std::mutex> lock(mutex_);
    selectStatus = true;
    return ErrorCode::SE_SUCCESS;
}

ErrorCode ChannelImpl::GetChannelNumber(uint8_t& channelNumber)
{
    std::lock_guard<std::mutex> lock(mutex_);
    channelNumber = channelNumber_;
    return ErrorCode::SE_SUCCESS;
}

ErrorCode ChannelImpl::SetChannelState(bool channelState)
{
    // NOTE: don't guard(mutex_) here again, do it CloseChannel.
    isChannelClosed_ = channelState;
    return ErrorCode::SE_SUCCESS;
}
}  // namespace SE
}  // namespace OHOS